#if 1

/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * pm.cc																	*
 *																			*
 * Physical page allocation													*
 *																			*
\***************************************************************************/

#include <system.h>
#include <memory.h>
#include <paging.h>
#include <stdio.h>
#include <pm.h>

/// GRUB MultiBoot header - memory map
struct pm_tmbmm {
	unsigned long size;
	unsigned long base_low;
	unsigned long base_high;
	unsigned long size_low;
	unsigned long size_high;
	unsigned long type;
};

/// The last free page found
uint_t lastfree = 0;

/// The memory bitmap - maps 4 GB
uchar_t membitmap[131072] = {0};

/// Initializes the page manager
void pm_init()
{
	// zero out the bitmap
	memset( membitmap, 0, 131072 );
}

/// Maps the memory bitmap properly (should be done AFTER all pm_set et al calls)
void pm_map()
{
	// map the bitmap with user privileges - i'm not 100% sure that this is necessary
	for( int i = ((unsigned int) membitmap); i <= (((unsigned int) membitmap)+131072); i += 0x1000 )
		MapPhysicalAddress( 0x9C000, i, i, PF_PRESENT | PF_READWRITE | PF_SUPERVISOR );
}

/// Fills affected pages from the memory map
void pm_fill_from_mmap( uint_t addr, uint_t len )
{
	// check for a valid memory map
	if( addr == 0 || len == 0 )
		return;
	
	// get the first entry
	pm_tmbmm* mm = (pm_tmbmm*) addr;
	
	// fill each invalid entry
	for( ; ((unsigned long) mm) < (addr + len); mm = (pm_tmbmm*) ((unsigned int) mm + mm->size + 4) )
	{
		// make sure it's not valid
		if( mm->type != 0x1 )
		{
			// make sure it's a page at least
			if( mm->size_low > 4095 )
				pm_set( mm->base_low & 0xFFFFF000, mm->size_low >> 12 );
		}
	}
}

/// Sets a range of pages as allocated
void pm_set( unsigned long page, unsigned long count )
{
	// get the starting page number (the first argument is an _address_)
	uint_t startnum = (page >> 12);
	
	// allocate them all
	int i;
	for( i = startnum; i < ( startnum + count ); i++ )
	{
		// is this page not allocated?
		if( ! ( membitmap[i/8] & (1<<(i%8)) ) )
		{
			// no - set it and return the memory
			membitmap[i/8] |= ( 1 << ( i % 8 ) );
		}
	}
}

/// Frees a single page
void pm_free( char* dat )
{
	// get the page number at which this resideds
	uint_t pnum = ((uint_t) dat) >> 12;
	
	// free it if it was allocated
	if( membitmap[pnum/8] & (1<<(pnum%8)) )
	{
		// no - set it and return the memory
		membitmap[pnum/8] &= ~( 1 << ( pnum % 8 ) );
	}
}

/// Allocates a single page
void* pm_alloc()
{
	// find a spare page
	int retpg = 0;
	for( retpg = lastfree; retpg < 1048576; retpg++ )
	{
		// is this page not allocated?
		if( ! ( membitmap[retpg/8] & (1<<(retpg%8)) ) )
		{
			// not allocated - set it and return the memory
			membitmap[retpg/8] |= ( 1 << ( retpg % 8 ) );
			return ((void*) (retpg * 4096));
		}
	}
	
	// none found, out of memory
	return (void*) 0;
}

/// Allocates a set of pages
void* pm_alloc( uint_t cnt )
{
	// find a spare page
	int retpg = 0;
	int found = 0;
	int tmp = 0;
	for( retpg = lastfree; retpg < 1048576; retpg++ )
	{
		// is this page not allocated?
		if( ! ( membitmap[retpg/8] & (1<<(retpg%8)) ) )
		{
			// search inside this set
			found = 0;
			for( tmp = retpg; tmp < retpg + cnt; tmp++ )
			{
				// allocated?
				if( membitmap[tmp/8] & (1<<(tmp%8)) )
					break;
				
				// no, so increment the found count
				found++;
			}
			
			// what did we get?
			if( found == cnt )
			{
				// allocate them all
				for( tmp = retpg; tmp < retpg + cnt; tmp++ )
				{
					// allocate it
					membitmap[tmp/8] |= ( 1 << ( tmp % 8 ) );
				}
				
				// return the base
				return ((void*) (retpg*4096));
			}
		}
	}
	
	// none found, out of memory
	return (void*) 0;
}

#else /// Obsolete code

/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * pm.cc																	*
 *																			*
 * Page management - courtesy of Kevin McGuire								*
 *																			*
\***************************************************************************/

// note from pcmattman: I haven't gone through this file (unlike pm.h)

#include <system.h>
#include <pm.h>
#include <stdio.h>
#include <paging.h>

unsigned long *pm_dir = 0;
unsigned long ____pm_slack = 0;

#ifdef DEBUG_INFO
#define KDBGINFO kprintf("%s:%s:%u:", __FILE__, __FUNCTION__, __LINE__); kprintf
#else
#define KDBGINFO //
#endif
#ifdef DEBUG_WARN
#define KDBGWARN kprintf("%s:%s:%u:", __FILE__, __FUNCTION__, __LINE__); kprintf
#else
#define KDBGWARN //
#endif
#ifdef DEBUG_ERROR
#define KDBGERROR kprintf("%s:%s:%u:", __FILE__, __FUNCTION__, __LINE__); kprintf
#else
#define KDBGERROR //
#endif


#define PM_TES_MAX 16
struct pm_tes
{
   unsigned long page;
   unsigned long count;
   unsigned long code;
};

pm_tes ____pm_tes[PM_TES_MAX];
unsigned char ____pm_tes_cnt = 0;

/// specialized initialization function
int pm_init_with_grub_mmap(unsigned long mmap_addr, unsigned long mmap_length)
{
   KDBGINFO("Called; mmap_addr:%x mmap_length:%x\n", mmap_addr, mmap_length);
   if((mmap_addr | mmap_length) == 0)
   {
      KDBGERROR("No memory map found.\n");
      // problems!
      return -1;
   }
   pm_tmbmm *mm = (pm_tmbmm*)mmap_addr;
   for(; ((unsigned long)mm) < (mmap_addr + mmap_length); mm = (pm_tmbmm*)((unsigned int)mm + mm->size + 4))
   {
      KDBGINFO("base:%x size:%x type:%x\n", mm->base_low, mm->size_low, mm->type);
      if( mm->type == 0x1 ){
         // hand range to pm_init function. (we should always get whole pages; if not oh well..)
         if(mm->size_low > 4095)
         {
            pm_init(mm->base_low & 0xFFFFF000, mm->size_low >> 12);
         }else{
            KDBGWARN("memory region size below required 4096 byte; memory not accounted.\n");
         }
      }
   }
   KDBGINFO("initialization complete.\n");
   return 1;
}

/// build dynamic tree to track 4GB of memory.
void pm_init(unsigned long page, unsigned long count)
{
   unsigned long *tbl;
   if(count == 1)
   {
      KDBGWARN("mem region ignored because of size.\n");
      return;
   }
   if(page == 0)
   {
      page += 0x1000;
      --count;
   }
   for(unsigned int x = 0; x < count; ++x)
   {
      for(unsigned int y = 0; y < ____pm_tes_cnt; ++y)
      {
         if(((page + (x * 4096)) >= ____pm_tes[y].page) && ((page + (x * 4096)) < (____pm_tes[y].page + (____pm_tes[y].count * 4096))))
         {
            // page is protected by ____pm_tes at this moment..
            goto nn;
         }
      }
   if(pm_dir == 0)
   {
      pm_dir = (unsigned long*)(page+(x*4096));
		if( !IsMapped( read_cr3(), page+(x*4096) ) )
			MapPhysicalAddress( read_cr3(), page+(x*4096), page+(x*4096), PF_PRESENT | PF_READWRITE | PF_USER );
      for(x = 0; x < 1024; ++x)
      {
         pm_dir[x] = 0;
      }
      continue;
   }
      if(!____pm_slack)
      {
         ____pm_slack = page + (x * 4096);
      }else{
         if(!pm_dir[(page + (x * 4096))>>22])
         {
            pm_dir[(page + (x * 4096))>>22] = ____pm_slack;
            tbl = (unsigned long*)____pm_slack;
		if( !IsMapped( read_cr3(), ____pm_slack ) )
			MapPhysicalAddress( read_cr3(), ____pm_slack, ____pm_slack, PF_PRESENT | PF_READWRITE | PF_USER );
            for(unsigned int y = 0; y < 1024; ++y)
            {
               tbl[y] = 0;
            }
         }else{
            tbl = (unsigned long*)(pm_dir[(page + (x * 4096))>>22] & 0xFFFFF000);
         }
         tbl[(page + (x * 4096))<<10>>10>>12] = PM_VALID;
      }
	nn:;
   }
   return;
}

int pm_free(unsigned long page)
{
   unsigned long *tbl;
   if(!pm_dir[page>>22])
   {
      KDBGINFO("page never existed: %x\n", page);
      // this page must have never been in this system to begin with, a bug causing this bad call?
      return -1;
   }
   tbl = (unsigned long*)(pm_dir[page>>22] & 0xFFFFF000);
   if(!tbl[page<<10>>10>>12])
   {
      KDBGINFO("page never existed: %x\n", page);
      // this page must have never been in this system to begin with, a bug causing this bad call?
      return -1;
   }
   // clear all bits, but the valid one.
   tbl[page<<10>>10>>12] = PM_VALID;
   return 1;
}
int pm_free(unsigned long page, unsigned long count)
{
   int result;
   for(unsigned int x = 0; x < count; ++x)
   {
      result |= pm_free(page + (x * 4096));
   }
   return result;
}
void pm_set_flush()
{
   if(!pm_dir)
   {
      KDBGWARN("can not flush with no accounted memory.\n");
      return;
   }
   for(unsigned int x = 0; x < ____pm_tes_cnt; ++x)
   {
      KDBGINFO("flushed cmd in tes (%x,%x,%x)\n", ____pm_tes[x].page, ____pm_tes[x].count, ____pm_tes[x].code);
      pm_set(____pm_tes[x].page, ____pm_tes[x].count, ____pm_tes[x].code);
   }
   ____pm_tes_cnt = 0;
}
void pm_set(unsigned long page, unsigned long count, unsigned long code)
{
   unsigned long *tbl;
   if(!pm_dir)
   {
      KDBGINFO("saved cmd in tes (%x,%x,%x)\n", page, count, code);
      // save command for during initialization and after.
      ____pm_tes[____pm_tes_cnt].page = page;
      ____pm_tes[____pm_tes_cnt].count = count;
      ____pm_tes[____pm_tes_cnt].code = code;
      ++____pm_tes_cnt;
      if(____pm_tes_cnt > PM_TES_MAX)
      {
         // display error, and halt machine hopefully! (should happen in early boot stages)
         KDBGERROR("____pm_tes_cnt exceeded PE_TES_MAX\n");
         for(;;);
      }
      return;
   }
   for(unsigned long x = 0; x < count; ++x)
   {
      if(!pm_dir[page>>22])
      {
         pm_dir[page>>22] = pm_alloc(1);
         if(pm_dir[page>>22] == 0)
         {
            KDBGWARN("____pm_tes cmd failed; no memory.\n");
            continue;
         }
		if( !IsMapped( read_cr3(), pm_dir[page>>22] ) )
			MapPhysicalAddress( read_cr3(), pm_dir[page>>22], pm_dir[page>>22], PF_PRESENT | PF_READWRITE | PF_USER );
         tbl = (unsigned long*)(pm_dir[page>>22] & 0xFFFFF000);
         for(unsigned int y = 0; y < 1024; ++y)
         {
            tbl[y] = 0;   
         }
      }else{
         tbl = (unsigned long*)(pm_dir[page>>22] & 0xFFFFF000);
      }
      tbl[(page + (x * 4096))<<10>>10>>12] = code;
   }
   return;
}
unsigned long pm_alloc(unsigned long from, unsigned long to, unsigned long count, unsigned long tag)
{
   unsigned long origin;
   unsigned long *tbl;
   unsigned long found = 0;
   to += 4096;
   origin = from;
   // keep bugs and bad calls from screwing us up..hopefully.
   tag = tag & (~PM_FLAG_MASK);
   from = from & 0xFFFFF000;
   to = to & 0xFFFFF000;
   // search for free pages.
   for(unsigned long page = from; page <= to; page += 4096)
   {
      if(pm_dir[page>>22])
      {
         tbl = (unsigned long*)(pm_dir[page>>22] & 0xFFFFF000);
         //kprintf("[%x:%x]", page, tbl[page<<10>>10>>12]);
         if(
            (tbl[page<<10>>10>>12] & PM_VALID) &&
            !(tbl[page<<10>>10>>12] & PM_USED)
            )
         {
            ++found;
            if(found >= count)
            {
               // allocate pages, now.
               for(page = origin; page < (origin + (4096 * count)); page += 4096)
               {
                  tbl = (unsigned long*)(pm_dir[page>>22] & 0xFFFFF000);
                  tbl[page<<10>>10>>12] = PM_VALID | PM_USED | tag;
               }
			   for( found = 0; found < count; found++ )
			   {
					if( !IsMapped( read_cr3(), origin + (found*0x1000) ) )
						MapPhysicalAddress( read_cr3(), origin + (found*0x1000), origin + (found*0x1000), PF_PRESENT | PF_READWRITE | PF_USER );
				}
               return origin;
            }
         }else{
            found = 0;
            // if next page is valid and free then we already have it's address instead of adding
            // a additional if (above) to check the value of origin.
            origin = page + 0x1000;
         }
      }else{
         // skip the 4mb table (it does not exist - not worth continually checking again and again)
         page = page + (4096 * 1024);
      }
   }
   // we just never allocate page address zero; use it for something else such as: gdt, idt ...
   return 0;
}
unsigned long pm_alloc(unsigned long from, unsigned long to, unsigned long count)
{
   // no tag.
   return pm_alloc(from, to, count, 0);
}
unsigned long pm_alloc(unsigned long count)
{
   // no range specification.
   return pm_alloc(0x1000, 0xEFFFF000, count);
}
unsigned long pm_alloc()
{
   // no count specified.
   return pm_alloc(1);
}

#endif
